#pragma once
#include <cstring>
#include <poll.h>
#include <unistd.h>
#include "socket.hpp"
/*
    多路转接之poll
*/
class PollServer
{
    const static int default_num = 10;
    const static int default_fd = -1;
    const static int default_timeout = 5000;

private:
    void dispatch()
    {
        for (int i = 0; i < _fdnum; i++)
        {
            if (_pollfd_arry[i].fd == default_fd) // 非法文件描述符
                continue;

            if (_pollfd_arry[i].revents & POLLIN) // 读事件检测（合法描述符不一定有事件就绪）
            {
                if (_pollfd_arry[i].fd == _listensocket->getsockfd())
                {
                    // 监听套接字的读事件就绪，此时有新的链接到来
                    listenServer();
                    debugPrintLegFd();
                }
                else
                {
                    // 普通文件描述符读事件就绪
                    ioServer(i);
                }
            }

            if (_pollfd_arry[i].revents & POLLOUT) // 写事件检测
            {
                //...（扩展）
            }
        }
    }

    void listenServer() // 获取新的tcp链接
    {
        struct sockaddr_in srcaddr;
        int newfd = _listensocket->Accept(&srcaddr);
        if (newfd < 0)
        {
            LOG(WARNING, "listensocket accept error!\n");
            return;
        }
        // 下面采用这样的方式可以避免当对端关闭链接的时候，可以避免数组元素移动调整带来的开销
        for (int i = 0; i < _fdnum; i++) // 在集合中找到一个没用使用的文件描述符位置
        {
            if (_pollfd_arry[i].fd == default_fd)
            {
                _pollfd_arry[i].fd = newfd;
                _pollfd_arry[i].events |= POLLIN; // 默认监听读事件
                return;
            }
        }

        // 文件描述符集合空间不够，需要扩容，但是不能直接扩容，因为listenServer此函数在dispatch函数里的循环中调用
        // 如果直接扩容，可能导致文件描述符的重复检测或者遗漏，
        // 在多线程情况下，有的线程在就空间依然访问，但是你却将旧空间释放，此时会带来问题
        // 异步扩容（能否采用redis异步保存的方式）
        //......................(待完成)
    }

    void ioServer(int index) // 处理读事件
    {
        char buf[1024] = {0};
        int n = recv(_pollfd_arry[index].fd, buf, sizeof(buf)-1, 0);
        if (n > 0)
        {
            LOG(INFO, "fd: %d, client say:%s\n", _pollfd_arry[index].fd, buf);
            string echo_msg = "server echo:";
            echo_msg += buf + string("\n");
            send(_pollfd_arry[index].fd, echo_msg.c_str(), echo_msg.size(), 0);
        }
        else if (n == 0)
        {
            // 对方关闭链接
            LOG(INFO, "client close, fd: %d\n", _pollfd_arry[index].fd);
            _pollfd_arry[index].fd = -1;
            close(_pollfd_arry[index].fd);
        }
        else
        {
            // 读异常
            LOG(INFO, "client error, fd: %d\n", _pollfd_arry[index].fd);
            _pollfd_arry[index].fd = -1;
            close(_pollfd_arry[index].fd);
        }
    }

    // 打印文件描述符集合中的合法fd
    void debugPrintLegFd()
    {
        cout << "legal fd:";
        for (int i = 0; i < _fdnum; i++)
        {
            if (_pollfd_arry[i].fd == default_fd)
                continue;
            cout << _pollfd_arry[i].fd << " ";
        }
        cout << endl;
    }

public:
    PollServer(uint16_t port) : _listensocket(make_unique<socket_ns::TcpSocket>()),
                                _fdnum(default_num), _timeout(default_timeout),
                                _pollfd_arry(new pollfd[_fdnum]),
                                _is_running(true)
    {
        struct sockaddr_in localaddr;
        bzero(&localaddr, sizeof(localaddr));
        localaddr.sin_family = AF_INET;
        localaddr.sin_addr.s_addr = INADDR_ANY;
        localaddr.sin_port = htons(port);

        // 1、创建套接字 2、绑定ip+port
        _listensocket->buildServerSocket((struct sockaddr *)&localaddr);

        // 3、初始化文件描述符集
        for (int i = 0; i < default_num; i++)
            _pollfd_arry[i].fd = default_fd;

        // 4、将监听套接字加入到文件描述符集中，并设置监听读事件
        _pollfd_arry[0].fd = _listensocket->getsockfd();
        _pollfd_arry[0].events |= POLLIN;
        LOG(INFO, "listen fd is ready!\n");
    }

    void loop()
    {
        while (_is_running)
        {
            int n = poll(_pollfd_arry, _fdnum, _timeout);
            switch (n)
            {
            case 0:
                LOG(INFO, "poll timeout!\n");
                break;
            case -1:
                LOG(ERROR, "poll error!\n");
                _is_running = false;
                break;
            default:
                LOG(INFO, "events is ready!\n");
                dispatch(); // 事件派发
                break;
            }
        }
    }

    void startServer()
    {
        _is_running = true;
    }

    void stopServer()
    {
        _is_running = false;
    }

    ~PollServer()
    {
        delete[] _pollfd_arry;
    }

private:
    unique_ptr<socket_ns::Socket> _listensocket;
    int _fdnum;                  // 文件描述符集的大小
    int _timeout;                // poll的阻塞事件，单位ms
    struct pollfd *_pollfd_arry; // 要监听的文件描述符集
    bool _is_running;            // 服务是否启动
};